﻿/*
 * This module is build on top of on J.Bradshaw's vmcController
 * Implements audio media library functions
 * 
 * Copyright (c) 2009 Anthony Jones
 * 
 * Portions copyright (c) 2007 Jonathan Bradshaw
 * 
 * This software code module is provided 'as-is', without any express or implied warranty. 
 * In no event will the authors be held liable for any damages arising from the use of this software.
 * Permission is granted to anyone to use this software for any purpose, including commercial 
 * applications, and to alter it and redistribute it freely.
 * 
 * History:
 * 2009-09-04 Reworked getArtistCmd to this, added caching by Anthony Jones
 * 
 */

using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;
using System.Collections.Generic;
using Microsoft.MediaCenter;

namespace VmcController.AddIn.Commands {
	/// <summary>
	/// Summary description for getArtists commands.
	/// </summary>
	public class musicCmd : ICommand {
		private static WMPLib.WindowsMediaPlayer Player = null;
		//private WMPLib.IWMPMedia media;
		//private WMPLib.IWMPPlaylist mediaPlaylist;

		private string debug_last_action = "none";

		public const int LIST_ARTISTS = 1;
		public const int LIST_ALBUMS = 2;
		public const int LIST_SONGS = 3;
		public const int LIST_DETAILS = 4;
		public const int PLAY = 5;
		public const int QUEUE = 6;
		public const int SERV_COVER = 7;
		public const int CLEAR_CACHE = 8;

		private int which_command = -1;

		private static Dictionary<string, string> m_templates = new Dictionary<string, string>();
		private state the_state = new state();
		private int result_count = 0;

		private static string CACHE_DIR = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
		                                  "\\VMC_Controller";

		private static string CACHE_MUSIC_CMD_DIR = CACHE_DIR + "\\music_cmd_cache";
		private static string CACHE_VER_FILE = CACHE_MUSIC_CMD_DIR + "\\ver";

		private const string DEFAULT_DETAIL_ARTIST_START = "artist=%artist%";
		private const string DEFAULT_DETAIL_ALBUM_START = "     album=%album% (%albumYear%; %albumGenre%)";

		private const string DEFAULT_DETAIL_SONG =
			"          %if-songTrackNumber%track=%songTrackNumber%. %endif%song=%song% (%songLength%)";

		private const string DEFAULT_DETAIL_ALBUM_END = "          total album tracks=%albumTrackCount%";
		private const string DEFAULT_DETAIL_ARTIST_END = "     total artist tracks=%artistTrackCount%";

		private const string DEFAULT_DETAIL_FOOTER =
			"total artists found=%artistCount%\r\ntotal albums found=%albumCount%\r\ntotal tracks found=%trackCount%";

		private static bool init_run = false;

		public musicCmd(int i) {
			which_command = i;

			if (!init_run) {
				init_run = true;
				loadTemplate();
				if (Player == null) {
					Player = new WMPLib.WindowsMediaPlayer();
				}
			}
		}

		#region ICommand Members

		/// <summary>
		/// Shows the syntax.
		/// </summary>
		/// <returns></returns>
		public string ShowSyntax() {
			string s =
				"[-help] [[exact_]artist:[*]artist_filter] [[exact_]album:[*]album_filter] [indexes:id1,id2] - list / play from audio collection";
			switch (which_command) {
				case LIST_ARTISTS:
					s = "[-help] [[exact_]artist:[*]artist_filter] [[exact_]album:[*]album_filter] - lists matching artists";
					break;
				case LIST_ALBUMS:
					s = "[-help] [[exact_]artist:[*]artist_filter] [[exact_]album:[*]album_filter] - list matching albums";
					break;
				case LIST_SONGS:
					s = "[-help] [[exact_]artist:[*]artist_filter] [[exact_]album:[*]album_filter] - list matching songs";
					break;
				case LIST_DETAILS:
					s =
						"[-help] [[exact_]artist:[*]artist_filter] [[exact_]album:[*]album_filter] [indexes:id1,id2] - lists info on matching songs / albums / artists";
					break;
				case PLAY:
					s =
						"[-help] [[exact_]artist:[*]artist_filter] [[exact_]album:[*]album_filter] [indexes:id1,id2] - plays matching songs";
					break;
				case QUEUE:
					s =
						"[-help] [[exact_]artist:[*]artist_filter] [[exact_]album:[*]album_filter] [indexes:id1,id2] - adds matching songs to the now playing list";
					break;
				case SERV_COVER:
					s =
						"[size-x:<width>] [size-x:<height>] [[exact_]artist:[*]artist_filter] [[exact_]album:[*]album_filter] [indexes:id1,id2] - serves the album cover of the first match";
					break;
				case CLEAR_CACHE:
					s = " - forces the cache to be cleared (normally only happens when the music library's length changes)";
					break;
			}
			return s;
		}

		public OpResult showHelp(OpResult or) {
			or.AppendFormat("music-list-artists [~artist-filter~] [~album-filter~] - lists all matching artists");
			or.AppendFormat("music-list-songs [~artist-filter~] [~album-filter~] - lists all matching songs");
			or.AppendFormat("music-list-albums [~artist-filter~] [~album-filter~] - lists all matching albums");
			or.AppendFormat("music-play [~artist-filter~] [~album-filter~] [~index-list~] - plays all matching songs");
			or.AppendFormat("music-queue [~artist-filter~] [~album-filter~] [~index-list~] - queues all matching songs");
			or.AppendFormat(
				"music-cover [~artist-filter~] [~album-filter~] [~index-list~] [size-x:width] [size-y:height] - serves the cover image (first match)");
			or.AppendFormat(" ");
			or.AppendFormat("Where:");
			or.AppendFormat("     [~artist-filter~] is one of:");
			or.AppendFormat(
				"          artist:<text> - matches artists that start with <text> (\"artist:ab\" would match artists \"ABBA\" and \"ABC\")");
			or.AppendFormat(
				"          artist:*<text> - matches artists that have any words that start with <text> (\"artist:*ab\" would match \"ABBA\" and \"The Abstracts\")");
			or.AppendFormat(
				"          exact-artist:<text> - matches the artist that exactly matches <text> (\"exact-artist:ab\" would only match an artist names \"Ab\")");
			or.AppendFormat("     [~album-filter~] is one of:");
			or.AppendFormat(
				"          album:<text> - matches albums that start with <text> (\"album:ab\" would match the album \"ABBA Gold\" and \"Abbey Road\")");
			or.AppendFormat(
				"          album:*<text> - matches albums that have any words that start with <text> (\"album:*ab\" would match \"Abbey Road\" and \"Let's Talk About Love\")");
			or.AppendFormat(
				"          exact_album:<text> - matches the album exactly named <text> (\"exact_album:ab\" would only match an album named \"Ab\")");
			or.AppendFormat("     [~index-list~] is of the form:");
			or.AppendFormat("          indexes:idx1,idx2... - specifies one or more specific songs returned by the filter");
			or.AppendFormat(
				"               Where idx1,idx2... is a comma separated list with no spaces (e.g. 'indexes:22,23,27')");
			or.AppendFormat(
				"     [size-x:~width~] - Resizes the served image, where ~width~ is the max width of the served image");
			or.AppendFormat(
				"     [size-y:~height~] - Resizes the served image, where ~height~ is the max height of the served image");
			or.AppendFormat(" ");
			or.AppendFormat("Parameter Notes:");
			or.AppendFormat(
				"     - Index numbers are just an index into the returned results and may change - they are not static!");
			or.AppendFormat("     - Both size-x and size-y must be > 0 or the original image will be returned without resizing.");
			or.AppendFormat(" ");
			or.AppendFormat(" ");
			or.AppendFormat("Examples:");
			or.AppendFormat("     music-list-artists - would return all artists in the music collection");
			or.AppendFormat(
				"     music-list-artists artist:b - would return all artists in the music collection whose name starts with \"B\"");
			or.AppendFormat(
				"     music-list-artists album:b - would return all artists in the music collection who have an album with a title that starts with \"B\"");
			or.AppendFormat("     music-list-albums artist:b - would return all albums by an artist whose name starts with \"B\"");
			or.AppendFormat(
				"     music-list-albums artist:b album:*b - would return all albums that have a word starting with \"B\" by an artist whose name starts with \"B\"");
			or.AppendFormat(
				"     music-list-songs exact-artist:\"tom petty\" - would return all songs by \"Tom Petty\", but not songs by \"Tom Petty and the Heart Breakers \"");
			or.AppendFormat(
				"     music-play exact_album:\"abbey road\" indexes:1,3 - would play the second and third songs (indexes are zero based) returned by the search for an album named \"Abbey Road\"");
			or.AppendFormat(
				"     music-queue exact-artist:\"the who\" - would add all songs by \"The Who\" to the now playing list");

			return or;
		}

		private struct state {
			public string artist;
			public string nextArtist;

			public int artistTrackCount;
			public int artistAlbumCount;
			public string artistAlbumList;
			public int artistCount;

			public string album;
			public int albumTrackCount;
			public string albumYear;
			public string albumGenre;
			public string albumImage;
			public string nextAlbum;

			public string song;
			public string songTrackNumber;
			public string songLength;
			public string songLocation;

			public int trackCount;
			public int albumCount;

			public void init() {
				artist = "";
				//artistIndex = 0;
				artistTrackCount = 0;
				artistAlbumCount = 0;
				artistAlbumList = "";
				artistCount = 0;
				nextArtist = "";

				album = "";
				//albumIndex = -1;
				albumTrackCount = 0;
				albumYear = "";
				albumGenre = "";
				albumImage = "";
				nextAlbum = "";

				song = "";
				songTrackNumber = "";
				songLength = "";
				songLocation = "";

				trackCount = 0;
				albumCount = 0;
			}

			public void resetArtist(string new_artist) {
				artist = new_artist;
				nextArtist = "";

				artistTrackCount = 0;
				artistAlbumCount = 0;
				artistAlbumList = "";
				artistCount++;
			}

			public void resetAlbum(string new_album) {
				album = new_album;
				nextAlbum = "";

				if (new_album.Length > 0) {
					albumList_add(new_album);
					artistAlbumCount++;
				}

				albumTrackCount = 0;
				albumYear = "";
				albumGenre = "";
				albumImage = "";

				//songTrackNumber = "";
				//songLength = "";
			}

			public void albumList_add(string s) {
				if (artistAlbumList.Length == 0) {
					artistAlbumList = s;
				}
				else {
					artistAlbumList = artistAlbumList + ", " + s;
				}
			}

			public void albumGenre_add(string s) {
				if (s == "") {
					return;
				}
				if (albumGenre.IndexOf(s) >= 0) {
					return;
				}
				if (albumGenre.Length == 0) {
					albumGenre = s;
				}
				else {
					albumGenre = albumGenre + ", " + s;
				}
			}

			public void findAlbumCover(string url) {
				string s = "";
				if (albumImage.Length > 0) {
					return;
				}

				try {
					s = Path.GetDirectoryName(url) + @"\Folder.jpg";
					if (File.Exists(s)) {
						albumImage = s;
					}
					else {
						s = Path.GetDirectoryName(url) + @"\AlbumArtSmall.jpg";
						if (File.Exists(s)) {
							albumImage = s;
						}
					}
				}
				finally {}
			}

			public void add_song_to_album(WMPLib.IWMPMedia media_item) {
				if (albumYear == "" || albumYear.Length < 4) {
					albumYear = media_item.getItemInfo("WM/OriginalReleaseYear");
				}
				if (albumYear == "" || albumYear.Length < 4) {
					albumYear = media_item.getItemInfo("WM/Year");
				}
				albumGenre_add(media_item.getItemInfo("WM/Genre"));

				if (albumImage.Length == 0) {
					findAlbumCover(media_item.sourceURL);
				}
				albumTrackCount++;
				artistTrackCount++;

				song = media_item.getItemInfo("Title");
				songLocation = media_item.sourceURL;
				songTrackNumber = media_item.getItemInfo("WM/TrackNumber");
				songLength = media_item.durationString;

				string s = media_item.getItemInfo("WM/AlbumTitle");
				if (album != s) {
					nextAlbum = s;
				}

				s = media_item.getItemInfo("WM/AlbumArtist");
				if (s == "") {
					s = media_item.getItemInfo("Author");
				}
				if (artist != s) {
					nextArtist = s;
				}

				return;
			}
		}

		public string do_conditional_replace(string s, string item, string v) {
			debug_last_action = "Conditional replace: Start - item: " + item;

			string value = "";
			try {
				value = v;
			}
			catch (Exception) {
				value = "";
			}

			if (value == null) {
				value = "";
			}
			else {
				value = value.Trim();
			}

			int idx_start = -1;
			int idx_end = -1;
			debug_last_action = "Conditional replace: Checking Conditional - item: " + item;
			while ((idx_start = s.IndexOf("%if-" + item + "%")) >= 0) {
				if (value.Length == 0) {
					if ((idx_end = s.IndexOf("%endif%", idx_start)) >= 0) {
						s = s.Substring(0, idx_start) + s.Substring(idx_end + 7);
					}
					else {
						s = s.Substring(0, idx_start);
					}
				}
				else {
					if ((idx_end = s.IndexOf("%endif%", idx_start)) >= 0) {
						s = s.Substring(0, idx_end) + s.Substring(idx_end + 7);
					}
					s = s.Substring(0, idx_start) + s.Substring(idx_start + ("%if-" + item + "%").Length);
				}
			}
			debug_last_action = "Conditional replace: Doing replace - item: " + item;
			s = s.Replace("%" + item + "%", value);

			debug_last_action = "Conditional replace: End - item: " + item;

			return s;
		}

		public string file_includer(string s) {
			int idx_start = -1;
			int idx_end = -1;
			string fn = null;
			while ((idx_start = s.IndexOf("%file-include%")) >= 0) {
				if ((idx_end = s.IndexOf("%endfile%", idx_start)) >= 0) {
					fn = s.Substring((idx_start + ("%file-include%".Length)), (idx_end - (idx_start + ("%file-include%".Length))));
				}
				else {
					fn = s.Substring(idx_start + "%file-include%".Length);
				}
				fn = fix_escapes(fn);

				string file_content = null;
				FileInfo fi = new FileInfo(fn);
				if (!fi.Exists) {
					file_content = "";
				}
				else {
					StreamReader include_stream = File.OpenText(fn);
					file_content = include_stream.ReadToEnd();
					include_stream.Close();
				}
				s = s.Substring(0, idx_start) + file_content + s.Substring(idx_end + "%endfile%".Length);
			}
			return s;
		}

		private string basic_replacer(string s, string item, string value, int count, int index) {
			if (s.Length > 0) {
				s = do_conditional_replace(s, item, value);
			}
			s = do_conditional_replace(s, "resultCount", String.Format("{0}", count));
			if (index >= 0) {
				s = do_conditional_replace(s, "index", String.Format("{0}", index));
			}

			return s;
		}

		private string replacer(string s, int index) {
			/* Artist */
			s = do_conditional_replace(s, "artist", the_state.artist);
			s = do_conditional_replace(s, "artistTrackCount", String.Format("{0}", the_state.artistTrackCount));
			s = do_conditional_replace(s, "artistAlbumCount", String.Format("{0}", the_state.artistAlbumCount));
			s = do_conditional_replace(s, "artistCount", String.Format("{0}", the_state.artistCount));
			s = do_conditional_replace(s, "nextArtist", the_state.nextArtist);
			s = do_conditional_replace(s, "artistAlbumList", the_state.artistAlbumList);

			/* Album*/
			s = do_conditional_replace(s, "album", the_state.album);
			//    s = s.Replace("%nextAlbum%", nextAlbum);
			s = do_conditional_replace(s, "albumTrackCount", String.Format("{0}", the_state.albumTrackCount));
			s = do_conditional_replace(s, "albumYear", the_state.albumYear);
			s = do_conditional_replace(s, "albumGenre", the_state.albumGenre);
			s = do_conditional_replace(s, "albumImage", the_state.albumImage);
			s = do_conditional_replace(s, "albumYear", the_state.albumYear);
			s = do_conditional_replace(s, "albumGenre", the_state.albumGenre);
			s = do_conditional_replace(s, "nextAlbum", the_state.nextAlbum);

			/* Song */
			s = do_conditional_replace(s, "song", the_state.song);
			s = do_conditional_replace(s, "songPath", the_state.songLocation);
			s = do_conditional_replace(s, "songTrackNumber", the_state.songTrackNumber);
			s = do_conditional_replace(s, "songLength", the_state.songLength);
			s = s.Replace("%index%", String.Format("{0}", index));

			s = do_conditional_replace(s, "trackCount", String.Format("{0}", the_state.trackCount));
			s = do_conditional_replace(s, "albumCount", String.Format("{0}", the_state.albumCount));

			return s;
		}

		private OpResult do_detailed_list(OpResult or, WMPLib.IWMPMedia media_item, int index, string template) {
			string artist = "";
			string album = "";
			bool added = false;

			if (media_item != null) {
				artist = media_item.getItemInfo("WM/AlbumArtist");
				if (artist == "") {
					artist = media_item.getItemInfo("Author");
				}
				album = media_item.getItemInfo("WM/AlbumTitle");
			}

			// End of artist?
			if (artist != the_state.artist) {
				if (the_state.album.Length > 0) {
					or.AppendFormat("{0}", replacer(getTemplate(template + ".Album-", DEFAULT_DETAIL_ALBUM_END), index));
				}
				if (the_state.artist.Length > 0) {
					or.AppendFormat("{0}", replacer(getTemplate(template + ".Artist-", DEFAULT_DETAIL_ARTIST_END), index));
				}
				if (index >= 0) {
					the_state.resetArtist(artist);
					the_state.resetAlbum(album);
				}
				if (media_item != null) {
					the_state.add_song_to_album(media_item);
					added = true;
					if (the_state.artist.Length > 0) {
						or.AppendFormat("{0}", replacer(getTemplate(template + ".Artist+", DEFAULT_DETAIL_ARTIST_START), index));
					}
					if (the_state.album.Length > 0) {
						or.AppendFormat("{0}", replacer(getTemplate(template + ".Album+", DEFAULT_DETAIL_ALBUM_START), index));
					}
				}
			}
				// End of album?
			else if (album != the_state.album) {
				if (the_state.album.Length > 0) {
					or.AppendFormat("{0}", replacer(getTemplate(template + ".Album-", DEFAULT_DETAIL_ALBUM_END), index));
				}
				if (index >= 0) {
					the_state.resetAlbum(album);
				}
				if (media_item != null) {
					the_state.add_song_to_album(media_item);
					added = true;
					if (the_state.album.Length > 0) {
						or.AppendFormat("{0}", replacer(getTemplate(template + ".Album+", DEFAULT_DETAIL_ALBUM_START), index));
					}
				}
			}

			// Do track:
			if (media_item != null) {
				if (!added) {
					the_state.add_song_to_album(media_item);
				}
				or.AppendFormat("{0}", replacer(getTemplate(template + ".Entry", DEFAULT_DETAIL_SONG), index));
			}

			return or;
		}

		public string trim_parameter(string param) {
			if (param.Substring(0, 1) == "\"") {
				param = param.Substring(1);
				if (param.IndexOf("\"") >= 0) {
					param = param.Substring(0, param.IndexOf("\""));
				}
			}
			else if (param.IndexOf(" ") >= 0) {
				param = param.Substring(0, param.IndexOf(" "));
			}

			return param;
		}

		private bool loadTemplate() {
			bool ret = true;

			try {
				Regex re = new Regex("(?<lable>.+?)\t+(?<format>.*$?)");
				StreamReader fTemplate = File.OpenText("music.template");
				string sIn = null;
				while ((sIn = fTemplate.ReadLine()) != null) {
					Match match = re.Match(sIn);
					if (match.Success) {
						m_templates.Add(match.Groups["lable"].Value, match.Groups["format"].Value);
					}
				}
				fTemplate.Close();
			}
			catch {
				ret = false;
			}

			return ret;
		}

		private string fix_escapes(string s) {
			s = s.Replace("\r", "\\r");
			s = s.Replace("\n", "\\n");
			s = s.Replace("\t", "\\t");

			return s;
		}

		private string getTemplate(string template, string default_template) {
			string tmp = "";

			if (!m_templates.ContainsKey(template)) {
				return default_template;
			}

			tmp = m_templates[template];
			tmp = tmp.Replace("\\r", "\r");
			tmp = tmp.Replace("\\n", "\n");
			tmp = tmp.Replace("\\t", "\t");

			tmp = file_includer(tmp);

			return tmp;
		}

		public string make_cache_fn(string fn) {
			fn = fn.Replace("\\", "_");
			fn = fn.Replace(":", "_");
			fn = fn.Replace(" ", "%20");
			fn = fn.Replace("\"", "_");

			return fn;
		}

		public bool check_cache(string cur_ver) {
			bool ret_val = true;
			string cache_ver = "";

			try {
				cache_ver = System.IO.File.ReadAllText(CACHE_VER_FILE);
				if (cur_ver != cache_ver) {
					ret_val = false;
				}
			}
			catch (Exception) {
				ret_val = false;
			}

			if (!ret_val) {
				clear_cache();
			}

			return ret_val;
		}

		public void clear_cache() {
			try {
				System.IO.Directory.Delete(CACHE_MUSIC_CMD_DIR, true);
			}
			catch (Exception) {
				return;
			}
		}

		public void save_to_cache(string fn, string content, string cur_ver) {
			check_cache(cur_ver);

			string cached_file = CACHE_MUSIC_CMD_DIR + "\\" + fn;

			try {
				//Create dir if needed:
				if (!Directory.Exists(CACHE_DIR)) {
					Directory.CreateDirectory(CACHE_DIR);
				}
				if (!Directory.Exists(CACHE_MUSIC_CMD_DIR)) {
					Directory.CreateDirectory(CACHE_MUSIC_CMD_DIR);
				}

				FileInfo fi = new FileInfo(CACHE_VER_FILE);
				if (!fi.Exists) {
					System.IO.File.WriteAllText(CACHE_VER_FILE, cur_ver);
				}

				System.IO.File.WriteAllText(cached_file, content);
			}
			catch (Exception) {
				return;
			}

			return;
		}

		public string get_cached(string fn, string cur_ver) {
			string cached = "";

			if (!check_cache(cur_ver)) {
				return "";
			}

			try {
				string cached_file = CACHE_MUSIC_CMD_DIR + "\\" + fn;

				FileInfo fi = new FileInfo(cached_file);
				if (fi.Exists) {
					cached = System.IO.File.ReadAllText(cached_file);
				}
			}
			catch (Exception e) {
				return "";
			}

			return cached;
		}

		/// <summary>
		/// Executes the specified param.
		/// </summary>
		/// <param name="param">The param.</param>
		/// <param name="result">The result.</param>
		/// <returns></returns>
		public OpResult Execute(string param) {
			debug_last_action = "Execute: Start";

			DateTime startTime = DateTime.Now;

			OpResult opResult = new OpResult();
			opResult.StatusCode = OpStatusCode.Ok;

			bool bFirst = false;
			int size_x = 0;
			int size_y = 0;

			string template = "";
			string cache_fn = make_cache_fn(String.Format("{0}-{1}.txt", which_command, param));
			string cache_body = "";
			bool is_cached = false;

			try {
				if (param.IndexOf("-help") >= 0) {
					opResult = showHelp(opResult);
					return opResult;
				}
				//if (Player == null) Player = new WMPLib.WindowsMediaPlayer();

				WMPLib.IWMPMediaCollection2 collection = (WMPLib.IWMPMediaCollection2) Player.mediaCollection;
				int ver = Player.mediaCollection.getByAttribute("MediaType", "Audio").count; //.GetHashCode();
				string cache_ver = String.Format("{0}", ver);
				WMPLib.IWMPQuery query = collection.createQuery();
				WMPLib.IWMPPlaylist mediaPlaylist = null;
				WMPLib.IWMPMedia media_item;

				ArrayList a_idx = new ArrayList();

				bool b_query = false;

				debug_last_action = "Execution: Parsing params";

				if (param.Contains("exact-artist:")) {
					string artist = param.Substring(param.IndexOf("exact-artist:") + "exact-artist:".Length);
					artist = trim_parameter(artist);
					query.addCondition("Artist", "Equals", artist);
					b_query = true;
				}
				else if (param.Contains("artist:*")) {
					string artist = param.Substring(param.IndexOf("artist:*") + "artist:*".Length);
					artist = trim_parameter(artist);
					query.addCondition("Artist", "BeginsWith", artist);
					query.beginNextGroup();
					query.addCondition("Artist", "Contains", " " + artist);
				}
				else if (param.Contains("artist:")) {
					string artist = param.Substring(param.IndexOf("artist:") + "artist:".Length);
					artist = trim_parameter(artist);
					query.addCondition("Artist", "BeginsWith", artist);
					b_query = true;
				}

				if (param.Contains("exact-album:")) {
					string album = param.Substring(param.IndexOf("exact-album:") + "exact-album:".Length);
					album = trim_parameter(album);
					query.addCondition("WM/AlbumTitle", "Equals", album);
					b_query = true;
				}
					//else if (param.Contains("album:*"))
					//{
					//    string artist = param.Substring(param.IndexOf("album:*") + "album:*".Length);
					//    if (album.IndexOf(" ") >= 0) album = album.Substring(0, album.IndexOf(" "));
					//    query.addCondition("WM/AlbumTitle", "BeginsWith", album);
					//    query.beginNextGroup();
					//    query.addCondition("WM/AlbumTitle", "Contains", " " + album);
					//}
				else if (param.Contains("album:")) {
					string album = param.Substring(param.IndexOf("album:") + "album:".Length);
					album = trim_parameter(album);
					query.addCondition("WM/AlbumTitle", "BeginsWith", album);
					b_query = true;
				}

				// Indexes specified?
				if (param.Contains("indexes:")) {
					string indexes = param.Substring(param.IndexOf("indexes:") + "indexes:".Length);
					if (indexes.IndexOf(" ") >= 0) {
						indexes = indexes.Substring(0, indexes.IndexOf(" "));
					}
					string[] s_idx = indexes.Split(',');
					foreach (string s in s_idx) {
						if (s.Length > 0) {
							a_idx.Add(Int16.Parse(s));
						}
					}
				}

				// Cover size specified?
				if (param.Contains("size-x:")) {
					string tmp_size = param.Substring(param.IndexOf("size-x:") + "size-x:".Length);
					if (tmp_size.IndexOf(" ") >= 0) {
						tmp_size = tmp_size.Substring(0, tmp_size.IndexOf(" "));
					}
					size_x = Convert.ToInt32(tmp_size);
				}
				if (param.Contains("size-y:")) {
					string tmp_size = param.Substring(param.IndexOf("size-y:") + "size-y:".Length);
					if (tmp_size.IndexOf(" ") >= 0) {
						tmp_size = tmp_size.Substring(0, tmp_size.IndexOf(" "));
					}
					size_y = Convert.ToInt32(tmp_size);
				}
				// Use Custom Template?
				if (param.Contains("template:")) {
					template = param.Substring(param.IndexOf("template:") + "template:".Length);
					if (template.IndexOf(" ") >= 0) {
						template = template.Substring(0, template.IndexOf(" "));
					}
				}

				if (which_command == PLAY) {
					bFirst = true;
				}

				switch (which_command) {
					case CLEAR_CACHE:
						clear_cache();
						return opResult;
					case LIST_ARTISTS:
						cache_body = get_cached(cache_fn, cache_ver);
						if (cache_body.Length == 0) {
							WMPLib.IWMPStringCollection artists = collection.getStringCollectionByQuery("Artist", query, "Audio", "Artist",
							                                                                            true);
							result_count = artists.count;
							opResult.AppendFormat("{0}", basic_replacer(getTemplate(template + ".H", ""), "", "", result_count, -1));
								// Header
							for (int j = 0; j < artists.count; j++) {
								//opResult.AppendFormat("artist={0}", artists.Item(j));
								opResult.AppendFormat("{0}",
								                      basic_replacer(getTemplate(template + ".H", "artist=%artist%"), "artist", artists.Item(j),
								                                     result_count, -1)); // Header
							}
						}
						else {
							is_cached = true;
							opResult.ContentText = cache_body;
						}
						break;
					case LIST_ALBUMS:
						cache_body = get_cached(cache_fn, cache_ver);
						if (cache_body.Length == 0) {
							WMPLib.IWMPStringCollection albums = collection.getStringCollectionByQuery("WM/AlbumTitle", query, "Audio",
							                                                                           "WM/AlbumTitle", true);
							result_count = albums.count;
							opResult.AppendFormat("{0}", basic_replacer(getTemplate(template + ".H", ""), "", "", result_count, -1));
								// Header
							for (int j = 0; j < albums.count; j++) {
								//opResult.AppendFormat("album={0}", albums.Item(j));
								opResult.AppendFormat("{0}",
								                      basic_replacer(getTemplate(template + ".H", "album=%album%"), "album", albums.Item(j),
								                                     result_count, -1)); // Header
							}
						}
						else {
							is_cached = true;
							opResult.ContentText = cache_body;
						}
						break;
					case LIST_SONGS:
						cache_body = get_cached(cache_fn, cache_ver);
						if (cache_body.Length == 0) {
							WMPLib.IWMPStringCollection songs = collection.getStringCollectionByQuery("Title", query, "Audio", "Artist", true);
							result_count = songs.count;
							opResult.AppendFormat("{0}", basic_replacer(getTemplate(template + ".H", ""), "", "", result_count, -1));
								// Header
							for (int j = 0; j < songs.count; j++) {
								//opResult.AppendFormat("index={0}, song={1}", j, songs.Item(j));
								opResult.AppendFormat("{0}",
								                      basic_replacer(getTemplate(template + ".H", "index=%index%, song=%song%"), "song",
								                                     songs.Item(j), result_count, j)); // Header
							}
						}
						else {
							is_cached = true;
							opResult.ContentText = cache_body;
						}
						break;
					case LIST_DETAILS:
					case PLAY:
					case QUEUE:
					case SERV_COVER:
						if (which_command == LIST_DETAILS) {
							cache_body = get_cached(cache_fn, cache_ver);
							if (cache_body.Length > 0) {
								is_cached = true;
								opResult.ContentText = cache_body;
								break;
							}
						}
						if (which_command == SERV_COVER || which_command == LIST_DETAILS) {
							the_state.init();
						}

						if (b_query) {
							mediaPlaylist = collection.getPlaylistByQuery(query, "Audio", "Artist", true);
						}
						else {
							mediaPlaylist = Player.mediaCollection.getByAttribute("MediaType", "Audio");
						}

						if (a_idx.Count > 0) {
							result_count = a_idx.Count;
						}
						else {
							result_count = mediaPlaylist.count;
						}

						// Header
						opResult.AppendFormat("{0}", basic_replacer(getTemplate(template + ".H", ""), "", "", result_count, -1));

						if (a_idx.Count > 0) {
							result_count = 0;
							for (int j = 0; j < a_idx.Count; j++) {
								try {
									media_item = mediaPlaylist.get_Item((Int16) a_idx[j]);
								}
								catch (Exception) {
									media_item = null;
								}
								if (media_item != null) {
									result_count++;
									if (which_command == LIST_DETAILS || which_command == SERV_COVER) // Display it
									{
										do_detailed_list(opResult, media_item, (Int16) a_idx[j], template);
										if (which_command == SERV_COVER) {
											photoCmd pc;
											pc = new photoCmd(photoCmd.SERV_PHOTO);
											return pc.getPhoto(the_state.albumImage, "jpeg", size_x, size_y);
										}
									}
									else // Play / Queue it
									{
										PlayMediaCmd pmc;
										pmc = new PlayMediaCmd(MediaType.Audio, !bFirst);
										bFirst = false;
										opResult = pmc.Execute(media_item.sourceURL);
									}
								}
							}
						}
						else {
							for (int j = 0; j < mediaPlaylist.count; j++) {
								media_item = mediaPlaylist.get_Item(j);
								if (which_command == LIST_DETAILS || which_command == SERV_COVER) // Display it
								{
									do_detailed_list(opResult, media_item, j, template);
									if (which_command == SERV_COVER) {
										photoCmd pc;
										pc = new photoCmd(photoCmd.SERV_PHOTO);
										return pc.getPhoto(the_state.albumImage, "jpeg", size_x, size_y);
									}
								}
								else // Play / Queue it
								{
									PlayMediaCmd pmc;
									pmc = new PlayMediaCmd(MediaType.Audio, !bFirst);
									bFirst = false;
									opResult = pmc.Execute(media_item.sourceURL);
								}
							}
						}
						if (which_command == LIST_DETAILS) {
							do_detailed_list(opResult, null, -1, template);
						}

						break;
				}

				// Footer
				if (!is_cached) {
					if (which_command != LIST_DETAILS) {
						opResult.AppendFormat("{0}",
						                      basic_replacer(getTemplate(template + ".F", "result_count=%resultCount%"), "", "",
						                                     result_count, -1));
					}
					else {
						opResult.AppendFormat("{0}", replacer(getTemplate(template + ".F", "result_count=%index%"), result_count));
					}
					//opResult.AppendFormat("result_count={0}", result_count);
					save_to_cache(cache_fn, opResult.ToString(), cache_ver);
				}
				string sub_footer =
					basic_replacer(getTemplate(template + ".C", "from_cache=%wasCached%\r\nellapsed_time=%ellapsedTime%"), "wasCached",
					               is_cached.ToString(), -1, -1);
				TimeSpan duration = DateTime.Now - startTime;
				sub_footer = basic_replacer(sub_footer, "ellapsedTime", String.Format("{0}", duration.TotalSeconds), -1, -1);
				opResult.AppendFormat("{0}", sub_footer);
				//opResult.AppendFormat("from_cache={0}", is_cached);
				//opResult.AppendFormat("ellapsed_Time={0}", duration.TotalSeconds);
			}
			catch (Exception ex) {
				opResult = new OpResult();
				opResult.StatusCode = OpStatusCode.Exception;
				opResult.StatusText = ex.Message;
				opResult.AppendFormat("{0}", debug_last_action);
				opResult.AppendFormat("{0}", ex.Message);
			}

			debug_last_action = "Execute: End";

			return opResult;
		}

		#endregion
	}
}